Dataset Agora Marketplace¶

Introduzione¶

Essendo un argomento molto particolare ho voluto introdurre un altro dataset contenente date che ho recuperato io stasso dal link gwern.net, sito che raccoglie dataset e dati relativi al Dark Web, ciò per dare del contesto preliminare per capre meglio il funzionamente dei merkati del deep Web, e per permettere di comprendere meglio i grafici che verranno visualizzati in seguitp.

Dataset Principale:¶

Il dataset principale che ho scelto è relativo al Marketplace "Agora". Il dataset che ho preso in considerazione presenta dati che si estendono tra il 2013 e il 2015. Esso presenta piu di 100.000 riche che rappresentano un inserzione per Prodotto presente all'interno del market. le colonne presenti all'interno di questo dataset sono:

  • Vendor
  • Category
  • Item
  • ItemDescription
  • Price
  • Origin
  • Destination
  • Rating
  • Remarks

Il dataset preso in esame é stato preso dal sito Kaggle al seguente link: https://www.kaggle.com/datasets/philipjames11/dark-net-marketplace-drug-data-agora-20142015?resource=download

Il dataset una volta scaricato e aperto risulta parecchio disordinato, ciò ha comportato un lavoro di parsing molto intenso, rimuovendo elementi NAN, eliminando spazzi superflui e dovendo collassare parecchie informazioni in altre che presentavano lo stesso tipo ma chiamato in modo diverso. grazie ad un utente di Kaggle ho potuto rendere un po più semplice il parsing del dataset quqesto utente pubblicato vari cosici che mi hanno permesso di prendere spunto per il mio parsing.

Dataset Secondario:¶

Il dataset secondario é stato creato a partire dal sito https://www.gwern.net/DNM-survival esso presenta alcune informazioni riguardanti la durata di vita dei più famosi Market tra cui anche Agora. Questo dataset mi é stato indispensabile per mostrare una caratteristica molto importante dei negozzi presenti nel Dark/Deep web, ovvero l'elevata volatilità, ciò rende importante ogni inserzione presente all'interno del dataset Agora, perché a differenza dei siti web tradizionali, quelli presenti nel dark web hanno una vita molto breve, e ogni inserzione può essere l'ultima, quindi ogiuna di esse deve essere considerata unica.

Premesse indispensabili:¶

Ci tengo a precisare ulteriormente che il mondo che andrò a trattare, avendo avuto la possibilità di approfondire tramite il mio lavoro (sviluppo software che vi permette la navigazione a scopo di indagini di polizzia) é un molto particolare. Il Dark Web e i market ad esso correlati non sono come gli shop che siamo abbituati a vedere nel Cler Web (ovvero l'internet che utilizziamo tutti i giorni), essi sembrano simili ma tutti sono ovviamente privi di garanzie ed ogni inserzione é unica è per questo motivo che il dataset presenta una linea per ogni inserzione. Differentemente dal Clear Web un determinato venditore non da la garanzia di affidabilità per i prodotti che vende ovvero se un venditore mette sul mercato un determinato prodotto l'unico modo per garantirne veridicità sono i rating degli utenti che l'hanno acquistato; ciò però non garantisce che se un prodotto venduto da un determinato venditre A sia un "buon" prodotto anche il nuovo articolo offerto all'interno del market, ciò significa che ogni inserzione riportata in questo dataset é importante.

Set-up generale¶

le operazioni preliminare che mi hanno permesso di lavorare con il dataset sono state:

  • parsing e raggruppamento delle colonne che presentavano lo stesso tipo di informazione ma chiamate in modo diverso
  • eliminazione delle colonne coon valori Nan
  • divisione delle categorie in colonne diverse per indicare le categorie dalla più generale alla più specifica.
  • Il parsing del rating in una colonna aparte che mostrava solo lo Score del prodotto.
In [1]:
# General
import numpy as np 
import pandas as pd
import plotly.express as px
import plotly
import plotly.graph_objs as go
import matplotlib.pyplot as plt
import chart_studio.plotly as py
import re

# Warnings OFF
import warnings
warnings.filterwarnings('ignore')

# Load
df = pd.read_csv("Agora.csv", encoding="latin1")
df.columns = [x.replace(" ", "") for x in df.columns] 
print("Data Load Complete")

# Change Dtype
df.Item = df.Item.astype(str)

"""
Pulizia Regex della colonna Destinazione e Origine. Ancora molto disordinato e ingannevole.

Spiegazione dell'espressione regolare:
- [a-zA-Z]{2} # Più di due lettere
- |[/] # Oppure, il carattere " / 
"""

for x in ["Origin","Destination"]:
    #1 Rimuovere le non-parole e privare le stringhe di spazi vuoti e tutti i caratteri in minuscolo e la prima lettera in maiuscolo.
    df[x] = df[x].str.capitalize().str.replace('[^\w\s]','')
    #2 Rimuovere le istanze di "solo". Informazioni ridondanti.
    df[x] = df[x].str.replace(r"\bonly\b", '').str.strip() # 2 
    #3 Trasformare tutte le iterazioni del termine "in tutto il mondo".
    df.loc[df[x].str.contains(r"(?i)world|\b(?i)word\b|(?i)global",na=False),x] = "Worldwide" # 3
    #4&5 Trasformare tutte le iterazioni di "united states" e "united kingdom".
    df.loc[df[x].str.contains(r"(?i)kingdom",na=False),x] = "Uk" # 4
    df.loc[df[x].str.contains(r"(?i)(\bunited states\b)|\b(?i)us\b",na=False),x] = "Usa" # 5
print("Cleaning of 'Origin' and 'Destination' Complete")
#

# rimozione del suffisso BTC
df["BTC"] = df['Price'].str.replace('BTC', '')

# Remove nan
df = df[pd.notnull(df['BTC'])]

#Ordinamento di alcune colonne che rappresentano le destinazioni e le origini che presentano lo stesso nome.
df['Destination'] = df['Destination'].replace('Wordwide', 'Worldwide')
df['Destination'] = df['Destination'].replace('Worlwide', 'Worldwide')
df['Destination'] = df['Destination'].replace('Anywhere', 'Worldwide')
df['Destination'] = df['Destination'].replace('Everywhere', 'Worldwide')
df['Destination'] = df['Destination'].replace('Usa', 'USA')
df['Destination'] = df['Destination'].replace('Uk', 'UK')
df['Destination'] = df['Destination'].replace('Europe', 'EU')
df['Destination'] = df['Destination'].replace('Europe union', 'EU')
df['Destination'] = df['Destination'].replace('European union', 'EU')
df['Destination'] = df['Destination'].replace('Eu', 'EU')

regex1= r'[a-zA-Z]{2}|[/]'
df = df[~df['BTC'].str.contains(regex1)]
# To float.
df.BTC = pd.to_numeric(df.BTC)

##

# Convert to Dollar Value with 2013/2015 Average (399)
# Scaling, and Rounding
df["Value"] = (df.BTC * 399).round(3)
df["LogValue"]= np.log(df.Value).round(3)
print("Bitcoin/Value Variable Cleaned")


df["Score"]= df.Rating.str.split('/').str[0]
df["Deals"] = "NaN"
df["Deals"] = df.Score[df.Score.str.contains(r"(deal)", na=False)]

df.Score[df.Score.str.contains(r"(deal)", na=False)] = "NaN"
df.Score = df.Score.str.replace(r'(~)','')
df.Score = df.Score.astype(float)
df.Deals = df.Deals.str.extract('(\d+)')
print("Rating and Deal Variable Cleaned and Expanded")

"""
parse delle categorie
"""
df = pd.concat([df,df.Category.str.split('/', expand=True)], axis=1)
df = df.rename(columns={0: 'cat1', 1: 'cat2'})
df = df.drop(columns=[2])
df = df.drop(columns=[3])
Data Load Complete
Cleaning of 'Origin' and 'Destination' Complete
Bitcoin/Value Variable Cleaned
Rating and Deal Variable Cleaned and Expanded

Qui di seguito é possibile visionare tutte le colonne presenti nel dataset e i loro tipi

In [2]:
df.dtypes
Out[2]:
Vendor              object
Category            object
Item                object
ItemDescription     object
Price               object
Origin              object
Destination         object
Rating              object
Remarks             object
BTC                float64
Value              float64
LogValue           float64
Score              float64
Deals               object
cat1                object
cat2                object
dtype: object

Il dataset parsato e pulito alla fine risulta come segue:

In [3]:
df
Out[3]:
Vendor Category Item ItemDescription Price Origin Destination Rating Remarks BTC Value LogValue Score Deals cat1 cat2
0 CheapPayTV Services/Hacking 12 Month HuluPlus gift Code 12-Month HuluPlus Codes for $25. They are wort... 0.05027025666666667 BTC Torland NaN 4.96/5 NaN 0.050270 20.058 2.999 4.96 NaN Services Hacking
1 CheapPayTV Services/Hacking Pay TV Sky UK Sky Germany HD TV and much mor... Hi we offer a World Wide CCcam Service for En... 0.152419585 BTC Torland NaN 4.96/5 NaN 0.152420 60.815 4.108 4.96 NaN Services Hacking
2 KryptykOG Services/Hacking OFFICIAL Account Creator Extreme 4.2 Tagged Submission Fix Bebo Submission Fix Adju... 0.007000000000000005 BTC Torland NaN 4.93/5 NaN 0.007000 2.793 1.027 4.93 NaN Services Hacking
3 cyberzen Services/Hacking VPN > TOR > SOCK TUTORIAL How to setup a VPN > TOR > SOCK super safe enc... 0.019016783532494728 BTC NaN NaN 4.89/5 NaN 0.019017 7.588 2.027 4.89 NaN Services Hacking
4 businessdude Services/Hacking Facebook hacking guide . This guide will teach you how to hack Faceb... 0.062018073963963936 BTC Torland NaN 4.88/5 NaN 0.062018 24.745 3.209 4.88 NaN Services Hacking
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
109684 gonz324 Drugs/Opioids/Opium 1 gr purified Opium This Listing is for a gramm of redefined Opium... 0.14363729 BTC Germany NaN 4.91/5 NaN 0.143637 57.311 4.048 4.91 NaN Drugs Opioids
109685 cheqdropz Weapons/Fireworks Shipping Ticket in order for me to ship one of the guns you bo... 0.08680555 BTC Usa NaN [0 deals] NaN 0.086806 34.635 3.545 NaN 0 Weapons Fireworks
109686 SnowQueen Drugs/Opioids/Opium 0.50 GRAMS #4 White Afghani Heroin - FULL ESCROW 0.50 grams #4 White Afghani Heroin SnowQueen... 0.33641201 BTC Canada Worldwide [0 deals] NaN 0.336412 134.228 4.900 NaN 0 Drugs Opioids
109687 SnowQueen Drugs/Opioids/Opium 1.0 GRAMS #4 White Afghani Heroin - FULL ESCROW 1.0 grams #4 White Afghani Heroin SnowQueen ... 0.61165820 BTC Canada Worldwide [0 deals] NaN 0.611658 244.052 5.497 NaN 0 Drugs Opioids
109688 daydreamer Drugs/Opioids/Opium HEROIN STAMP BAG (10pcs) BUNDLE HEROIN STAMP BAG (10pcs) BUNDLE BEST AVAIL... 0.25491129 BTC Usa NaN 4.87/5 NaN 0.254911 101.710 4.622 4.87 NaN Drugs Opioids

109675 rows × 16 columns

Distribuzione della durata di vita dei Market¶

Il Grafico seguente prende in considerazione la durata di vita di dei market più famosi come Agora, SikRoad e molti altri calcolandone la durata di attività in giorni.

Questo grafico presenta punti interessanti che mi permette di capire quale é la tendenza e la durata di vita dei market, e come essi operano in questo mercato.

È possibile vedere molto chiaramente che la maggior parte dei negozzi hanno una vita molto breve che non supera i 200 giorni di attività, questo risultato é dovuto alla pratica, molto comune nel Dark Web che consiste nel creare siti per vendere un determinato prodotto e poi essere chiusi in tempi record, ai venditori importa guadagnare il più possibile nel minor tempo possibile, non vi é , come nei grandi marchi online come Amazon o Zalando, un prestigio dato dal nome del negozzio che salvo rarissimi casi ci si dimentica in poco tempo della sua esistenza.

In [4]:
dfLifetime=pd.read_excel("Cartel2.xlsx")

#histogram of mean of lifetime of the Market between the opening and the closing
dfLifetime["MeanLifetime"]=dfLifetime["End"]-dfLifetime["Start"]
dfLifetime["MeanLifetime"]=dfLifetime["MeanLifetime"].dt.days

display(dfLifetime.loc[dfLifetime['Market']=='Agora'])

fig=px.histogram(dfLifetime, x="MeanLifetime", nbins=20, marginal="box")
fig.update_layout(xaxis_title="Lifetime of the Market in days")
fig.update_layout(yaxis_title="number of Market")
fig.update_layout(title_text="Distribution of the mean of lifetime of the Market in days")
fig.update_layout(font=dict(size=15))
fig.show()
Market Start End Alive Closure Arrested URL Multisig I2P Bitcoin Litecoin Dogecoin Darkcoin Codebase Guns Fraud Hacks Doxed Notes MeanLifetime
67 Agora 2013-12-03 2015-09-06 False voluntary False agorahooawayyfoe.onion False False True False False False custom 1.0 1.0 0 False Forums: lacbzxobeprssrfx.onion. Agora banned g... 642.0

Ragioni per la chiusura dei negozzi online:¶

l'istogramma sottostante rappresenta le cause che hanno portato questi colossi del dark Web a chiudere. come possiamo ben vedere abbiamo delle categorie ben distinte:

  • scam (frode)
  • volontary (chiusura volontaria)
  • hacked (chiusura per hacking)
  • rided (chiusura per mancanza di utenti)

La causa principale di chiusura é dovuta agli scam ovvero truffe, suppongo sia dovuto alla presenza di truffatori sul sito compromettendone gli affare. Al secondo posto vi é la volontà, ciò significa che i market in questione hanno chiuso volontariamente il sito, per i motivi più disparati o molto probabilmente per la ragione citata in precedenza.

In [5]:
dfLifetime["Closure"].value_counts()
#histogram of Closure
fig=px.histogram(dfLifetime, x="Closure", nbins=20)
fig.update_layout(xaxis_title="Reason of the closure")
fig.update_layout(title_text="Distribution of the reason of the closure")
fig.show()

Agora motivo di chiusura:¶

Come possiamo individuare dal dataset il motivo di chiusura del market che prenderemo in considerazione tra poco é proprio la volontà, la ragione della chiusura è riportata do seguito:

In [6]:
#find Agora closed for legal reasons
display(dfLifetime.loc[dfLifetime["Market"]=="Agora",["Market","Closure"]])
s=dfLifetime.loc[dfLifetime["Market"]=="Agora",["Notes"]]
print(f"Ragiorne di chiusura di Agora:\n{s.iloc[0,0]}")
Market Closure
67 Agora voluntary
Ragiorne di chiusura di Agora:
Forums: lacbzxobeprssrfx.onion. Agora banned guns 2015-07-15 after ~20 gun busts became known; nevertheless, for most of its lifespan it allowed guns. Agora announced 2015-08-25 that it would be going offline indefinitely to deal with Tor deanonymization attacks; withdrawals were enabled and DNstats indicates the site went offline around 2015-09-06.

Agora market, Origine più comune dei prodotti:¶

La prima domanda che mi sono posto avendo in mano questo dataset e sapendo il grande alone di mistero che é presente su guesto argomento, è stata quale è la nazione da cui provengono la maggior parte dei prodotti presenti all'interno di Agora. Per poter raggiungere i dati necessari, essendo un dataset molto disordinato ho proceduto come segue:

  • ho eseguito una group by per poter raggruppare in maniera più utile le origini dei prodotti, così da avere l'origine seguita dal numero di prodotti provenienti da quel determinato luogo.
  • Infine ho realizzato un semplice istogramma per visualizzarne il contenuto.

qui di seguito è possibile vedere un l'istogramma dell'origine piu comuni dei prodotti all'interno di Agora. È possibile vedere che la maggior parte dei prodotti provengono dagli Stati Uniti, seguiti da Inghilterra e Australia, inoltre nella top 20 mostrata di seguito è possibile trovare anche Italia e Svizzera.

In [7]:
#group by origin
dfOrigin=df.groupby("Origin").count()
dfOrigin=dfOrigin.sort_values(by=['Vendor'], ascending=False)
dfOrigin=dfOrigin.reset_index()
dfOrigin=dfOrigin.rename(columns={"Vendor":"Count"})
dfOrigin=dfOrigin.head(20)
#histogram of the most common origins of products
fig=px.histogram(dfOrigin, y="Origin", x="Count",height=600)
fig.update_layout(yaxis_title="Origin")
fig.update_layout(xaxis_title="number of products")
fig.update_layout(title_text="Distribution of the most common origins of products")
fig.update_layout(yaxis={'categoryorder':'total ascending'})
fig.update_layout(font=dict(size=12.5))
fig.show()
#percentuale dell'origine dei prodotti

dfOrigin["Percent"]=dfOrigin["Count"]/dfOrigin["Count"].sum()*100
dfOrigin["Percent"]=dfOrigin["Percent"].round(2)
dfOrigin
#percentuale di prodotti con origine svizzera
display(dfOrigin.loc[dfOrigin["Origin"]=="Switzerland",["Origin","Percent"]])
display(dfOrigin.loc[dfOrigin["Origin"]=="Italy",["Origin","Percent"]])
Origin Percent
18 Switzerland 0.34
Origin Percent
15 Italy 0.54

Destinazione più comine dei prodotti¶

Dopo aver preso in considerazione l'origine dei prodotti, ho voluto vedere la destinazione più comune, per farlo ho proceduto come segue:

  • ho eseguito una group by per poter raggruppare in maniera più utile le destinazioni dei prodotti, così da avere la destinazione seguita dal numero di prodotti destinati a quel determinato luogo.
  • Infine ho realizzato un istogramma per visualizzarne il contenuto.

la sezione WorldWide comprendi tutti coloro che hanno GolGlobal o World, Cio significa che non vi é una destinazione precisa. Purtroppo il dataset presenta anche delle destinazioni che non sono utili come la voce You che non fornisce nessun'informazione aggiuntiva ai fini della ricerca e che ho proceduto ad eliminare.

In alcuni casi si può notare che ci sono prodotti che hanno destinazioni multiple, questo perché il venditore ha inserito più destinazioni per un determinato prodotto, per esempio un venditore potrebbe inserire un prodotto con destinazione WorldWide e USA. Non ho voluto eliminare questi dati perché potrebbero essere utili per un'analisi più approfondita.

Qui di seguito possiamo vedere la destinazione dei prodotti, come possiamo vedere la maggior parte dei prodotti hanno come destinazione WorldWide, seguita da USA e Eu(Europa).

In [8]:
#istogram of the most common origins of the products sold on Agora.

#group by destination
dfDestination=df.groupby("Destination").count()
dfDestination=dfDestination.sort_values(by=['Vendor'], ascending=False)
dfDestination=dfDestination.reset_index()
dfDestination=dfDestination.rename(columns={"Vendor":"Count"})
dfDestination=dfDestination.head(20)
dfDestination=dfDestination.drop([14])#drop raw 14 (You)

#histogram of the most common destinations of products
fig=px.histogram(dfDestination, y="Destination", x="Count",height=600)
fig.update_layout(yaxis_title="Destination")
fig.update_layout(xaxis_title="number of products")
fig.update_layout(title_text="Distribution of the most common destinations of products")
fig.update_layout(yaxis={'categoryorder':'total ascending'})
fig.show()

Per rendere più pratica l'esposizione del grafico durante la presentazione ho voluto creare un subplit che mostrava assieme l'origine e la destinazione dei prodotti.

In [9]:
fig = plotly.tools.make_subplots(rows=1, cols=2, specs=[[{"type": "bar"}, {"type": "bar"}]])
fig.add_trace(go.Bar(name="Origin",x=dfOrigin["Origin"], y=dfOrigin["Count"]),row=1, col=1)

fig.add_trace(go.Bar(name="Destination",x=dfDestination["Destination"], y=dfDestination["Count"]),row=1, col=2)
fig.update_layout(xaxis={'categoryorder':'total descending'})
fig.update_layout(height=600,title_text="Distribution of the most common destinations and origins of products")
fig.update_layout(font=dict(size=15))
fig.show()
C:\Users\tagli\miniconda3\envs\datascience\lib\site-packages\plotly\tools.py:460: DeprecationWarning:

plotly.tools.make_subplots is deprecated, please use plotly.subplots.make_subplots instead

Categorie di prodotti più comuni¶

Il Dark Web é un posto dove si possono trovare molte tipologie diverse di prodotti, Per questo motivo ho voluto vedere quali sono le categorie di prodotti più comuni all'interno di Agora, avendo così una visione più ampia di quale tipologia di prodotti sono più presenti in percentuale per poi andare più in profondità cercando nelle sottocategorie più specifiche per farlo ho proceduto come segue:

  • ho eseguito una group by per raggruppare in maniera più utile le categorie più generali dei prodotti, rappresentate dalla colonna cat1.
  • Infine ho realizzato un istogramma per visualizzarne il contenuto.

Qui di seguito possiamo vedere le categorie di prodotti più comuni con la categoria Drugs che rappresenta l' 84.82% dei prodotti presenti all'interno di Agora. Mi ha molto sorpreso che la categoria Weapons sia in posizione parecchio bassa rispetto a Drugs,cono scendo nel pensiero comune che il Dark Web é un posto dove poter comprare armi mi sare aspettato una seconda posizione, non avrei mai detto che la seconda categoria più comune sia Information.

In [10]:
#group by category
dfCategory=df.groupby("cat1").count()
dfCategory=dfCategory.sort_values(by=['Vendor'], ascending=False)
dfCategory=dfCategory.reset_index()
dfCategory=dfCategory.rename(columns={"Vendor":"Count"})
dfCategory=dfCategory.head(20)
#percentuale di prodotti per categoria
dfCategory["Percent"]=dfCategory["Count"]/dfCategory["Count"].sum()*100
dfCategory["Percent"]=dfCategory["Percent"].round(2)
dfCategory
#replace Info with Information
dfCategory["cat1"]=dfCategory["cat1"].replace("Info","Information")

#istogramma che mostra come la maggior parte dei prodotti sono di tipo drugs
fig=px.histogram(dfCategory, y="cat1", x="Percent",text_auto="percent",height=600)
fig.update_layout(yaxis_title=" Global Category")
fig.update_layout(xaxis_title="Percent of products")
fig.update_layout(title_text="Distribution of the most common categorys of products")
fig.update_layout(yaxis={'categoryorder':'total ascending'})
fig.update_layout(font=dict(size=15))
fig.show()

Categorie specifiche di prodotti più comuni:¶

Dopo aver visto le categorie più generali, ho voluto vedere quali sono le categorie più specifiche, per farlo ho proceduto come segue:

  • ho creato un metodo che prende in considerazione la colonna Categoery e la suddivide prendendo l'ultimo elemento che rappresenta la categoria più specifica.
  • ho eseguito una group by per raggruppare in maniera più utile le categorie più specifiche dei prodotti, rappresentate dalla colonna cat2.
  • Infine ho realizzato un istogramma per visualizzarne il contenuto.

All'interno del dataset vi sono le catecorie , per la maggior parte dei prodotti,espresse con una struttira a categorie e sotto categorie nella sezione seguente ho estrapolato tramite una funzione l'ultima categoria, quella che dovrebbe essere più specifica e l'ho salvata in una colonna detta "LastCategorys".

In [11]:
Categorys =[]
def f (s):
    return s.split("/")[-1]


df["LastCategorys"]=df["Category"].apply(f)
#group by LastCategorys
dfLastCategorys=df.groupby("LastCategorys").count()
dfLastCategorys=dfLastCategorys.sort_values(by=['Vendor'], ascending=False)
dfLastCategorys=dfLastCategorys.reset_index()
dfLastCategorys=dfLastCategorys.rename(columns={"Vendor":"Count"})
dfLastCategorys=dfLastCategorys.head(10)
#histogram of the most common LastCategorys of products
fig=px.histogram(dfLastCategorys, y="LastCategorys", x="Count")
fig.update_layout(yaxis_title="LastCategorys")
fig.update_layout(xaxis_title="number of products")
fig.update_layout(title_text="Distribution of the most common LastCategorys of products")
fig.update_layout(yaxis={'categoryorder':'total ascending'})
fig.show()

Analisi sui Venditori:¶

Dopo aver analizzato le categorie dei prodotti, ho voluto analizzare i venditori, e vedere se fosse presente una correlazioni tra il numero di inserzioni e il rating dei venditori, per farlo ho proceduto come segue:

  • inanzitutto ho proceduto ,per scrupolo, a verificare che vi fosse davvero una differenza tra le inserzionei dei vari venditori, come supponevo si vi era una differenza.

vediamo che le vi sono venditori che hanno moltissime inserzioni, mentre altri che ne posseggono davvero poche.

In [12]:
df["Vendor"].value_counts()
display(df["Vendor"].value_counts().head(10))
display(df["Vendor"].value_counts().tail(10))
optiman          881
sexyhomer        860
mssource         823
profesorhouse    804
RXChemist        729
rc4me            648
fake             608
medibuds         604
Gotmilk          479
Bigdeal100       451
Name: Vendor, dtype: int64
agorasmora          1
MarloEscobar        1
SantasHardAtWork    1
SwissShroomery      1
rcfreedom           1
buymyshrooms        1
Fivestartravel      1
Digitalpossi2014    1
eBay                1
GermanGBL           1
Name: Vendor, dtype: int64

Qui di seguito raggruppati in maniera migliore

In [13]:
#plot che mostra i vendor con il maggior numero di inserzioni
fig=px.bar(df["Vendor"].value_counts().head(10), x=df["Vendor"].value_counts().head(10).index, y=df["Vendor"].value_counts().head(10).values,text=df["Vendor"].value_counts().head(10).values)
fig.update_layout(yaxis_title="Number of insertions")
fig.update_layout(xaxis_title="Vendor")
fig.update_layout(title_text="Vendor with the most insertions")
fig.update_layout(font=dict(size=15))
fig.show()

I Venditori con più inserzioni sono davvero i migliori?¶

Avendo identificato la grande quantità di venditori presenti all'interno di Agora e la grande quantità di inserzioni ad essi correlate, ho voluto vedere se i venditori più grande, con un maggior numero di inserioni rispetto agli altri, sono davvero i migliori e di conseguenza i più affidabili. Per farlo ho proceduto come segue: ho deviso i venditori tra

  • Big Vendor
  • Medium Vendor
  • Small Vendor

in base al numero di inserzioni che hanno inserito all'interno di Agora, creando così tre gruppi di venditori isVendorBig(più di 400 inserzioni), isVendorMedium(tra 400 e 200) e isVendorSmall(meno di 200).

In [14]:
BigVendor=df["Vendor"].value_counts()>=400
df["isVendorBig"] = BigVendor[df["Vendor"]].values

MediumVendor=(df["Vendor"].value_counts()<400) & (df["Vendor"].value_counts()>=200)
df["isVendorMedium"] = MediumVendor[df["Vendor"]].values

SmallVendor=df["Vendor"].value_counts()<200
df["isVendorSmall"] = SmallVendor[df["Vendor"]].values

Venditori e valutazioni per differenti categorie¶

Possiamo notare che i tendenzialmente abbiamo tutti i tre tipo di venditori con molte inserzioni positive, anzi possiamo vedere come la mediana degli smal vendor sia quasi piu alta delle altre. Possiamo vedere inoltre che gli small vendor hanno più outlayer, suppongo perche vi siano più smal vendor che degli altri tipi.

In [15]:
#define if an articol is from a big medium or small vendor
df["isVendorBig"] = BigVendor[df["Vendor"]].values
df["isVendorMedium"] = MediumVendor[df["Vendor"]].values
df["isVendorSmall"] = SmallVendor[df["Vendor"]].values

trace1 = go.Box(
    x=df.loc[df["isVendorSmall"]==True,"Score"],
    name = 'Small Vendor',
    marker = dict(
        color = 'rgb(12, 128, 12)',
    )
)
trace2 = go.Box(
    x=df.loc[df["isVendorMedium"]==True,"Score"],
    name = 'Medium Vendor',
    marker = dict(
        color = 'rgb(12, 128, 128)',
    )
)
trace3 = go.Box(
   x=df.loc[df["isVendorBig"]==True,"Score"],
    name = 'Big Vendor',
    marker = dict(
        color = 'rgb(12, 12, 140)',
    )
)
data = [trace1, trace2,trace3]
fig = go.Figure(data=data)
fig.update_layout(title_text="Box plot of the score for the different type of vendors")
fig.update_layout(xaxis_title="Score")
fig.update_layout(font=dict(size=18))
fig.show()
 
In [16]:
'''count contare tutti i isVendorBig isVendorMedium isVendorSmall'''
display(df["isVendorBig"].value_counts())
display(df["isVendorMedium"].value_counts())
display(df["isVendorSmall"].value_counts())
False    100653
True       9022
Name: isVendorBig, dtype: int64
False    98610
True     11065
Name: isVendorMedium, dtype: int64
True     89588
False    20087
Name: isVendorSmall, dtype: int64

come è possibile vedere dai dati soprastanti vi sono più small Vendor rispetto agli altri.

per verificare che fosse vero che ci fossero venditori grandi con zero come rating sui loro prodotti ho eseguito una query che mi restituisce i venditori con più inserzioni e con zero come rating, come si può notare nella tabella seguente, c'é un venditore con più inserzioni con zero come rating.

premetto che i grafici mi sono serviti per verificare la veridicità dei dati, non sono grafici utili a mostrare qualsiasi alrte informazione.

In [17]:
display(df.loc[(df["isVendorBig"]==True) & (df["Score"]==0),["Vendor"]].count())
#vedere se RXChemist a quale categoria appartiene
display(df.loc[df["Vendor"]=="RXChemist",["Vendor","isVendorBig"]])

df.loc[df["Vendor"]=="RXChemist",["Vendor","Score"]].describe()
#grafico dell'andamento dei prezzi per il vendor RXChemist
fig=px.line(df.loc[df["Vendor"]=="RXChemist",["Vendor","Score","Item"]], y="Score", x="Item", height=1000)
fig.update_layout(title_text="Price trend for the vendor RXChemist")
fig.update_layout(font=dict(size=18))
fig.show()
Vendor    21
dtype: int64
Vendor isVendorBig
1375 RXChemist True
3134 RXChemist True
3142 RXChemist True
3143 RXChemist True
3146 RXChemist True
... ... ...
102944 RXChemist True
102945 RXChemist True
102949 RXChemist True
102958 RXChemist True
109281 RXChemist True

729 rows × 2 columns

Distribuzione delle valutazioni generali per i venditori¶

Per avere una visione più ampia delle valutazioni dei venditori, ho voluto vedere infine la distribuzione delle valutazioni generali per i venditori, per farlo ho proceduto come segue:

  • ho eseguito una group by raggruppando cosi gli score dei venditori.
  • Infine ho realizzato un istogramma per visualizzarne il contenuto.

l'istogramma che segue mostra la distribuzione delle valutazioni generali per i venditori, possiamo notare che la maggior parte dei cenditori ha uno Score compreso tra 4.9/5.0

In [18]:
#mean of the score for each vendor

df2=df.groupby("Vendor")["Score"].mean()
#remove nan
df2=df2.dropna()

fig=px.histogram(df2, x="Score",range_x=[0,5],nbins=100)
fig.update_layout(title_text="Distribution of the score of the vendors")
fig.update_layout(font=dict(size=18))
fig.show() 

Parole più comuni nei titoli dei prodotti¶

Per fare un analisi anche del linguaggio utilizzato dai venditori, ho voluto creare una WordCloud che mi mostrasse le parole più comuni. Per farlo ho proceduto come segue:

  • ho utilizzato la libreria WordCloud per creare la WordCloud.
  • ho procedudo allo split dei titoli dei prodotti per ottenere le singole parole.
  • ho creato una lista di stopword per rimuovere le parole più comuni.

infine ho creato la WordCloud che mi mostra le parole più comuni nei titoli dei prodotti.

In [19]:
from wordcloud import WordCloud, STOPWORDS

comment_words = ''
stopwords = set(STOPWORDS)
 
# iterate through the csv file
for val in df["Item"]:
     
    # typecaste each val to string
    val = str(val)
 
    # split the value
    tokens = val.split()

    for i in range(len(tokens)):
        for j in range(len(tokens[i])):
            if tokens[i][j]=="â":
                pass
    # Converts each token into lowercase
    for i in range(len(tokens)):
        if tokens[i]!= 'â' or tokens[i]!= "â":
            tokens[i] = tokens[i].lower()
     
    comment_words += " ".join(tokens)+" "
 
wordcloud = WordCloud(width = 800, height = 800,
                background_color ='white',
                stopwords = stopwords,
                min_font_size = 10).generate(comment_words)
 
# plot the WordCloud image                      
plt.figure(figsize = (8, 8), facecolor = None)
plt.imshow(wordcloud)
plt.axis("off")
plt.tight_layout(pad = 0)
 
plt.show()